from typing import Callable

from pyjvm.runtime.metadata import Method
from pyjvm.runtime.thread import Frame

_registry = {}


def _do_nothing(frame: Frame):
    pass


def _get_registry_key(class_name: str, method_name: str, method_descriptor: str) -> str:
    return class_name + '~' + method_name + '~' + method_descriptor


def register(class_name: str, method_name: str, method_descriptor: str,
             method: Callable):
    key = _get_registry_key(class_name, method_name, method_descriptor)
    _registry[key] = method


def get_native_method(class_name: str, method_name: str, method_descriptor: str) -> Callable:
    key = _get_registry_key(class_name, method_name, method_descriptor)

    if key in _registry:
        return _registry[key]

    if method_descriptor == '()V':
        if method_name in ('registerNatives', 'initIDs'):
            return _do_nothing


class PyJvmUnsupportedNativeMethodError(Exception):
    pass


def invoke_native(frame, method):
    native_method = get_native_method(method.klass.name, method.name, method.descriptor)
    if native_method is None:
        raise PyJvmUnsupportedNativeMethodError('PyJvm 暂不支持本地方法：%s %s.%s' %
                                                (method.descriptor, method.klass.name, method.name))

    native_method(frame)


def invoke_method(frame: Frame, method: Method):
    thread = frame.thread
    new_frame = thread.new_frame(method)
    thread.push_frame(new_frame)

    i = method.args_slot_count - int(method.is_static)
    for _type in reversed(method.parameter_types):
        slot = frame.operand_stack.pop()
        if _type in set('DJ'):
            i -= 1
        new_frame.local_vars[i] = slot
        i -= 1
    if not method.is_static:
        new_frame.local_vars[0] = frame.operand_stack.pop()


__all__ = ['register', 'invoke_method', 'invoke_native']
